package com.google.jessewilson.codesearch.util;

import ca.odell.glazedlists.EventList;
import ca.odell.glazedlists.event.ListEvent;
import ca.odell.glazedlists.event.ListEventListener;

import javax.swing.*;

/**
 * Performs additional processing to {@link EventList} elements
 * on the Swing event dispatch thread.
 *
 * @author jessewilson
 */
public class EventListEdtProcessor<E> implements ListEventListener<E> {

  private final Processor<E> processor;

  public EventListEdtProcessor(Processor<E> processor) {
    this.processor = processor;
  }

  /**
   * Install a new {@link EventListEdtProcessor} that processes the specified
   * target {@link EventList}.
   */
  public void install(EventList<E> target) {
    // perform initial processing of elements
    for(int i = 0; i < target.size(); i++) {
      process(target, target.get(i));
    }

    target.addListEventListener(this);
  }

  /**
   * Clean up resources allocated by this {@link EventListEdtProcessor}.
   */
  public void uninstall(EventList<E> target) {
    target.removeListEventListener(this);
  }

  /**
   * Process the specified element in the specified list.
   */
  private void process(final EventList<E> target, final E element) {
    // don't bother if it's already been processed
    if (processor.isProcessed(element)) {
      return;
    }

    // do processing asynchronously on the EDT
    SwingUtilities.invokeLater(new Runnable() {
      public void run() {
        // again, don't bother if it's already been processed
        if (processor.isProcessed(element)) {
          return;
        }

        // do the actual processing
        E processed = processor.process(element);

        // tell the EventList the element has been updated
        target.getReadWriteLock().writeLock().lock();
        try {
          // TODO(jessewilson): optimize this using an indexRef
          for(int i = 0; i < target.size(); i++) {
            if (target.get(i) == element) {
              target.set(i, processed);
            }
          }
        } finally {
          target.getReadWriteLock().writeLock().unlock();
        }
      }
    });
  }

  /**
   * When the source is changed, process all updated elements.
   */
  public void listChanged(ListEvent<E> listChanges) {
    while(listChanges.next()) {
      int index = listChanges.getIndex();
      int type = listChanges.getType();
      if (type == ListEvent.INSERT || type == ListEvent.UPDATE) {
        process(listChanges.getSourceList(), listChanges.getSourceList().get(index));
      }
    }
  }

  /**
   * Process elements on the UI thread.
   */
  public interface Processor<E> {

    /**
     * Returns true if this element doesn't need processing. This
     * method may be called by any thread.
     */
    boolean isProcessed(E element);

    /**
     * Process this element on the Swing Event Dispatch Thread.
     */
    E process(E element);
  }
}
